﻿#region Using directives
using System;
using System.Collections.Generic;
using System.Linq;

#endregion

namespace ProcolAI {
    public class SimilarityMatches {
        /* Prefs schema:
         * 
         * var projects = new Dictionary<string, Dictionary<string, double>>();
         * 
         * projects["Proyecto Uno"] = new Dictionary<String, double>();
         * projects["Proyecto Uno"]["Computacion"] = 2.5;
         * projects["Proyecto Uno"]["Mecatronica"] = 3.5;
         * projects["Proyecto Uno"]["Diseno"] = 3.0;
         * projects["Proyecto Uno"]["Ing. Industrial"] = 3.5;
         * projects["Proyecto Uno"]["Valores"] = 2.5;
         * projects["Proyecto Uno"]["Comercio"] = 3.0;
         */

        private SimilarityMatches() {
            throw new Exception("Private constructor");
        }

        public double DistanceSimilarity(IDictionary<string, Dictionary<String, double>> facts, String origin,
                                         String target) {
            var si = new Dictionary<String, double>();
            var P1 = facts[origin];
            var P2 = facts[target];

            foreach (var kvp in P1) {
                if (facts[target].ContainsKey(kvp.Key))
                    si[kvp.Key] = 1;
            }

            if (si.Count == 0)
                return 0;

            double sumOfSquares = 0;

            foreach (var kvp in si) {
                if (P2.ContainsKey(kvp.Key))
                    sumOfSquares += Math.Pow(P1[kvp.Key] - P2[kvp.Key], 2);
            }

            return 1 / (1 + Math.Sqrt(sumOfSquares));
        }

        private static double PearsonSimilarity(IDictionary<string, Dictionary<String, double>> facts, String origin,
                                                String target) {
            var si = new Dictionary<String, double>();
            try {
                var P1 = facts[origin];
                var P2 = facts[target];

                foreach (var kvp in P1) {
                    if (facts[target].ContainsKey(kvp.Key))
                        si[kvp.Key] = 1;
                }

                int n = si.Count;

                if (n == 0)
                    return 0;

                double sum1 = si.Select(kvp => P1[kvp.Key]).Sum();
                double sum2 = si.Select(kvp => P2[kvp.Key]).Sum();
                double sum1Sq = si.Select(kvp => Math.Pow(P1[kvp.Key], 2)).Sum();
                double sum2Sq = si.Select(kvp => Math.Pow(P2[kvp.Key], 2)).Sum();
                double pSum = si.Select(kvp => P1[kvp.Key] * P2[kvp.Key]).Sum();

                double num = pSum - ((sum1 * sum2) / n);
                double den = Math.Sqrt((sum1Sq - Math.Pow(sum1, 2) / n) * (sum2Sq - Math.Pow(sum2, 2) / n));

                return den == 0 ? 0 : num / den;
            } catch (KeyNotFoundException) {}

            return 0;
        }

        public static MatchEntry[] GetMatches(IDictionary<string, Dictionary<String, double>> facts, String origin,
                                              int matches) {
            var dict = new List<MatchEntry>();

            foreach (var kvp in facts) {
                if (!String.Equals(origin, kvp.Key))
                    dict.Add(new MatchEntry { Id = kvp.Key, Value = PearsonSimilarity(facts, origin, kvp.Key) });
            }

            dict.Sort((p1, p2) => p1.Value.CompareTo(p2.Value));
            dict.Reverse();

            return (from i in dict
                    select i).Take(matches).ToArray();
        }

        #region Nested type: MatchEntry
        public class MatchEntry {
            public string Id { get; set; }
            public double Value { get; set; }
            public string Extra { get; set; }

            public override string ToString() {
                return string.Format("Id: {0}, Value: {1}", Id, Value);
            }
        }
        #endregion
    }
}